package com.example.seckillconsumer.controller;

import com.example.seckillconsumer.feign.SecKillFeign;
import com.example.seckillconsumer.feign.SecKillOrderFeign;
import com.example.seckillconsumer.service.RedisMessagePublisher;
import com.example.seckillconsumer.utils.GetUidUtil;
import com.example.seckillconsumer.utils.JedisUtil;
import com.myshop.b2b.commonmodule.Goods;
import com.myshop.b2b.commonmodule.vo.Result;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author likaiping
 * @createTime 2024/7/6 20:42
 */
@RestController
@AllArgsConstructor
@RequestMapping("/seckill")
public class SecKillController {

    private RedisMessagePublisher redisMessagePublisher;
    private SecKillFeign secKillFeign;
    private SecKillOrderFeign secKillOrderFeign;

    private final String secKillScript =
            "local userid=KEYS[1]; \n" +
            "local prodid=KEYS[2];\n" +
                    "local qtkey=\"secKill_\"..prodid;\n" +
                    "local usersKey=\"secKill_\"..prodid..\"_userList\"; \n" +
                    "local userExists=redis.call(\"sismember\",usersKey,userid);\n" +
                    "if tonumber(userExists)==1 then \n" +
                    "  return 2;\n" +
                    "end\n" +
                    "local num= redis.call(\"get\" ,qtkey);\n" +
                    "if tonumber(num)<=0 then \n" +
                    "  return 0; \n" +
                    "else \n" +
                    "  redis.call(\"decr\",qtkey);\n" +
                    "  redis.call(\"sadd\",usersKey,userid);\n" +
                    "end\n" +
                    "return 1;";

    /**
     * 加入LUA脚本优化后的秒杀接口
     * @param gid 产品id
     * @return /
     */
    @GetMapping("/startSecKillWithLua")
    public Result secKillForRedisWithLua(HttpServletRequest request, @RequestParam("gid") String gid, @RequestParam("userId") String userId) {
        String keyName = "secKill_" + gid;
        String originKeyName = "secKill_" + gid + "_origin";
        String userKeyList = "secKill_" + gid + "_userList";
        String token = request.getHeader("Authorization");

        Jedis jedis = JedisUtil.getJedis();
        String scriptLoad = jedis.scriptLoad(secKillScript);

        //判断是否开始秒杀
        if (jedis.get(keyName) == null || jedis.get(keyName).equals("")) {
            System.out.println("秒杀还未开始，请耐心等待");
            return Result.error("秒杀还未开始，请耐心等待");
        }

        if (userId.equals("0") || userId == null) {
             userId = String.valueOf(new Random().nextInt(10000));
        }else if (userId.equals("1")){
            //userId传过来为1表示是用户登录后发送秒杀请求 0表示用来测试高并发随机生成userId
            userId = String.valueOf(GetUidUtil.getUaccount(token));
        }
        Object result = jedis.evalsha(scriptLoad, 2, userId, gid);
        String reString = String.valueOf(result);
        try {
            switch (reString) {
                case "0":
                    System.out.println("秒杀已结束");
                    int num = Integer.parseInt(jedis.get(keyName));
                    if (num == 0 && jedis.get(originKeyName) != null) {
                        //originKeyName为秒杀商品的最初数量 该资源在使用一次后就会释放掉 所以为空时其他线程无法进入 则不会去尝试加锁并修改数据库
                        int originNum = Integer.parseInt(jedis.get(originKeyName));
                        //尝试加锁
                        String lockKey = "lock:"+gid;
                        String lockValue = UUID.randomUUID().toString(); //生成唯一值，确保锁的独占性 防止其他线程误删锁
                        SetParams params = new SetParams().nx().ex(5000);//锁的过期时间，单位毫秒
                        //尝试获取锁，设置过期时间为5s 以setnx设置，当该锁不存在时才能设置锁即持有锁
                        if ("OK".equals(jedis.set(lockKey,lockValue,params))) {
                            try {
                                //确保当前线程持有锁
                                //修改库存
                                if (secKillFeign.editGoodsStock(Integer.valueOf(gid),originNum)) {
                                    //释放秒杀商品最初数量资源 因为已经对数据库作了修改
                                    jedis.del(originKeyName);
                                    System.out.println("mysql库存已修改");

                                    //增加订单信息
                                    Set<String> smembers = jedis.smembers(userKeyList);
                                    Set<Integer> members = smembers.stream().map(Integer::valueOf).collect(Collectors.toSet());
                                    List<Integer> uIds = members.stream().collect(Collectors.toList());
                                    System.out.println(uIds);
                                    Goods goods = secKillOrderFeign.getOnegid(Integer.parseInt(gid));
                                    if (secKillOrderFeign.insertSecKillOrder(goods.getGname(),1,goods.getGprice(),uIds)) {
                                        System.out.println("秒杀订单信息已添加");
                                    }
                                }else {
                                    System.out.println("mysql库存修改失败");
                                }
                            } finally {
                                //释放锁前，确认当前线程持有的锁 未持有就不能把锁释放掉，每个线程都有自己的lockValue，但是reids之中只有一个唯一的lockValue
                                if (lockValue.equals(jedis.get(lockKey))) {
                                    jedis.del(lockKey);
                                }
                            }
                        }else {
                            System.out.println("秒杀已结束，其他线程正在进行同步数据源操作，已加锁，请稍后再试");
                        }
                    }
                    return Result.success("秒杀已结束","end");
                case "1":
                    System.out.println("用户 " + userId + " 秒杀成功");
                    return Result.success("用户 " + userId + " 秒杀成功","success");
                case "2":
                    System.out.println(userId + "该用户已秒杀过，不能再次秒杀");
                    return Result.success(userId + "该用户已秒杀过，不能再次秒杀","failed");
                default:
                    System.out.println("秒杀异常");
                    return Result.error("秒杀异常");
            }
        } finally {
            jedis.close();
        }
//        return Result.success("秒杀成功");
    }

    /**
     * 设置秒杀产品的信息
     * @param gid 商品id
     * @param secKillNum 秒杀库存数量
     * @return /
     */
    @GetMapping("/setSecKillProduct")
    public Result setSecKillProduct(@RequestParam("gid") int gid, @RequestParam("secKillNum") int secKillNum) {
        Jedis jedis = JedisUtil.getJedis();
        String keyName = "secKill_" + gid;
        String originKeyName = "secKill_" + gid + "_origin";
        String orderUserList = keyName + "_userList";

        jedis.setex(keyName,300, String.valueOf(secKillNum));
        jedis.setex(originKeyName,300,String.valueOf(secKillNum));
        if (jedis.exists(orderUserList)) {
            jedis.del(orderUserList);
        }
        //发布消息到seckill频道
        redisMessagePublisher.publishSeckillMessage(String.valueOf(gid));
        return Result.success("添加产品秒杀信息成功");

    }

    @GetMapping("/getGoodsStocks")
    public Result getGoodsStock(@RequestParam("gid") Integer gid) {
        System.out.println("进入seckill-consumer的controller的getGoodsStock");
        System.out.println("gid:"+gid);
        if (secKillFeign == null) {
            return Result.error("secKillFeign为空");
        }
        Integer goodsStock = secKillFeign.getGoodsStock(gid);
        if (goodsStock == null) {
            return Result.error("获取库存失败");
        }
        return Result.success("获取库存成功",goodsStock);
    }

    @GetMapping("/setGoodsStock")
    public Result setGoodsStock(@RequestParam("gid") Integer gid,@RequestParam("stock") Integer stock) {
        boolean setGoodsStock = secKillFeign.setGoodsStock(gid, stock);
        if (!setGoodsStock) {
            return Result.error("设置库存失败");
        }
        return Result.success("设置库存成功");
    }

    @GetMapping("/editGoodsStock")
    public Result editGoodsStock(@RequestParam("gid") Integer gid,@RequestParam("stock") Integer stock) {
        boolean editGoodsStock = secKillFeign.editGoodsStock(gid, stock);
        if (!editGoodsStock) {
            return Result.error("编辑库存失败");
        }
        return Result.success("编辑库存成功");
    }
}
